#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <time.h>

// NETWORK
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>

// New includes
#include "util.h"
#include "network.h"

/*
 * THIS IS THE REQUESTER
 * NOTE: the receiver acts as a client and the multiple senders act
 * as various servers that the receiver needs to connect to.
 *
 * The RECEIVER is initialized using the following command:
 *
 * requester -p <port> -o <file option>
 */

// CONFIGRATION VARIABLES
#define NUM_RECEIVER_ARGS 11
//#define CURR_HOST "mumble-31.cs.wisc.edu" //"localhost"
//#define CURR_OUT_PORT 5000

int in_port;          // Port on which the requester waits for packets
char *file_option;    // Name of the file that is being requested
char hostname[256];  // Host name of the machine that the
                      // requester runs on
int window;
char *f_hostname;
int f_port; 



void dg_recv(int sockfd, struct sockaddr_in*pcliaddr, socklen_t clilen)
{

    FILE * tracker;
    tracker = fopen("tracker.txt","r");
    
    if (tracker == NULL){
	perror(ERROR_FILEREAD);
	exit(1);
    }

    char filename[256];
    int id;
    char sender_hostname[256];
    int sender_port;
    int packet_count = 0;
    int size = 0;  // Used to keep track of total data sent
    struct timeval start;
    struct timeval nextTime;

    while(fscanf(tracker, "%s %d %s %d", filename, &id,
    					sender_hostname,
					&sender_port) != EOF){

        if(strcmp(filename, file_option) !=0){
            continue;
        }

	// FOR TESTING PURPOSES
	//printf("\n%d\n", sender_port);

	int firstPacket = 0;
	Window_Element  receivedWindow[window];
	
        struct hostent *f_host;

	// SET UP DESTINATION INFORMATION
	f_host = gethostbyname(f_hostname);
        bzero(pcliaddr, sizeof(*pcliaddr));
        pcliaddr->sin_family = AF_INET;
        pcliaddr->sin_port = htons(f_port);
        bcopy((char *)f_host->h_addr,
        (char *)&(pcliaddr->sin_addr.s_addr), f_host->h_length);

	// CAST BUFFERS
        em_packet = (Em_Packet*)em_packet_buf;
	packet = (Packet*)packet_buf;
	
	// LOAD BUFFERS
	load_packet(packet, 'R', id, window, filename);

	load_em_packet(em_packet,
			1,
			hostname,
			in_port,
                        sender_hostname,
			sender_port,
                        sizeof(Packet) + strlen(packet->payload),
			packet); 

	// SEND THE REQUEST PACKET
        int n;
	print_time();
	if ((n = sendto(sockfd, em_packet_buf,
                        sizeof(Em_Packet) + em_packet->length,
                        0,(struct sockaddr*) pcliaddr, clilen)) == -1)
	{
            perror(ERROR_SENDTO);
	    exit(1);
	}

	// PRINT WHAT WAS SENT (to verify contents)
	print_em_packet((Em_Packet*)em_packet_buf);
	print_sent_packet((Packet*)((Em_Packet*)em_packet_buf)->payload, pcliaddr, n);

	//printf("before receiving requested file\n");

        while(1) 
        {
	    int end_received = 0;
	    int windowCount = 0;
            if(size == 0){
	        firstPacket = 1;
            }

	    int i;
	    int curr_seq = -1;

	    for(i=0;i<window;i++){
		receivedWindow[i].sent = 0;
		//printf("CALLLED\n");
	    }

	    //printf("before receiving window\n");

            while(windowCount < window){

	      //printf("waiting for next packet\n");

                // START RECEIVE ACTION
                if ((n = recvfrom(sockfd, em_packet_buf,
                          MAX_PACKET_SIZE, 0,
                         (struct sockaddr*)pcliaddr, &clilen)) == -1)
                {
                    perror(ERROR_RECVFROM);
	            exit(1);
	        }
                print_time();

		//printf("received packet\n");
		
		// Set the total timer
	        if(firstPacket == 1){
                    start = get_time();
		    firstPacket = 0;
	        }
	        nextTime = get_time();
	    
                em_packet_buf[n] = '\0'; // Null terminate
	        size += n;              // To increase total bytes sent

		// CAST THE NEW PACKET
                em_packet = (Em_Packet*)em_packet_buf;
                packet = (Packet*)em_packet->payload;

		// PRINT THE RECEIVED PACKET
		print_em_packet(em_packet);
	        print_recv_packet(packet, pcliaddr, n, size);
		curr_seq = packet->sequence;

                if(packet->type == 'E'){
                    nextTime = get_time();
	            end_received = 1;
                    break;
	        }

                packet_count++;

		//printf("Before window memcpy\n");

		// Check if this window element was already received
		if(receivedWindow[(packet->sequence - 1) % window].sent == 0){
		  memcpy(receivedWindow[(packet->sequence - 1) % window].em_packet, (char*) em_packet, sizeof(Em_Packet) + em_packet->length);
		  receivedWindow[(packet->sequence - 1)%window].em_packet[sizeof(Em_Packet) + em_packet->length] = '\0';
		  receivedWindow[(packet->sequence - 1) % window].sent = 1;
		    windowCount++;

		    //printf("sequence mod: %d\n", packet->sequence%window);
		    //printf("sequence sent?: %d\n", receivedWindow[packet->sequence % window].sent);
		}
		
		em_packet = (Em_Packet*) receivedWindow[(packet->sequence - 1) % window].em_packet;

		packet = (Packet*) em_packet->payload;

	        //printf("%s\n", packet->payload);
		

		//printf("After window memcpy\n");

		// Set up the ACK packet
		packet = (Packet*)packet_buf;
                em_packet = (Em_Packet*)em_packet_buf;
                load_packet(packet, 'A', curr_seq, 0, "");

		// Currently wrong (need to set dest port and IP from
		// the received packet
	        load_em_packet_IP(em_packet,
                             1, 
                             (char*)&em_packet->dest_IP,
                             em_packet->dest_port,
                             (char*)&em_packet->src_IP,
                             em_packet->src_port,
                             sizeof(Packet) + strlen(packet->payload),
                             packet);

		//print_em_packet((Em_Packet*)em_packet_buf);
		//print_sent_packet((Packet*)((Em_Packet*)em_packet_buf)->payload, pcliaddr, n);

        	int n;
		print_time();
		
		//printf("sending ACK");
		// Send the ACK
		if ((n = sendto(sockfd, em_packet_buf,
                        sizeof(Em_Packet) + em_packet->length,
                        0,(struct sockaddr*) pcliaddr, clilen)) == -1)
		{
            		perror(ERROR_SENDTO);
	    		exit(1);
		}

		

		// Print what was sent
		print_em_packet((Em_Packet*)em_packet_buf);
		print_sent_packet((Packet*)((Em_Packet*)em_packet_buf)->payload, pcliaddr, n);

		//print_em_packet(em_packet);
		//print_sent_packet((Packet*)packet_buf,
            	//pcliaddr, n);
	        
		// Reset the cliaddr
                f_host = gethostbyname(hostname);
                bzero(pcliaddr, sizeof(*pcliaddr));
                pcliaddr->sin_family = AF_INET;
                pcliaddr->sin_port = htons(in_port);
                bcopy((char *)f_host->h_addr,
                (char *)&(pcliaddr->sin_addr.s_addr), f_host->h_length);
            }

	    if(end_received == 1 && window == 1){
                nextTime = get_time();
		window = 0;
	    }

	    FILE *output;
	    output = fopen(filename, "a");
	    if (output < 0)
	      {
		printf("FILE OPEN ERROR\n");
		exit(1);
	      }

	    /*
	    if(end_received == 1){
                fclose(output);

		}*/

	    // RENAME THIS EVENTUALLY
	    int colin = -1;
	    //printf("\nWINDOW (%d) #: %d\n", colin, window);
	    for(i = 0; i < window && colin != 0; i++){
	      //	    printf("\nWINDOW (%d) #: %d\n", colin, window);
        	em_packet = (Em_Packet*)receivedWindow[i].em_packet;
		//em_packet->payload[em_packet->length] = '\0';
		packet = (Packet*)em_packet->payload;

		//printf("%s", packet->payload);
		colin = fprintf(output, packet->payload);
		//printf("count: %d (i = %d)\n", colin, i);
	    }

            fclose(output);

	    if(end_received == 1){
                nextTime = get_time();
		break;
	    }

        }

        printf("Total data pkts recv:\t%d\n", packet_count);
        printf("Total bytes recv:\t%d\n", size);

        double totalTime = (double)nextTime.tv_sec+(((double)nextTime.tv_usec)*.000001) - (double)start.tv_sec+(((double)start.tv_usec)*.000001);

        printf("Avg pkt per sec:\t%f\n", totalTime/packet_count);
        printf("Duration of test:\t%f\n", totalTime);

        size = 0;
        packet_count = 0;
    }

    fclose(tracker);
}

int main(int argc, char *argv[])
{
    int sockfd;
    int parse_code;

    struct sockaddr_in servaddr;
    struct sockaddr_in cliaddr;
    struct hostent *server;

    // Verify number of command line arguments
    if (argc != NUM_RECEIVER_ARGS)
    {
        perror(ERROR_REQUESTER_USAGE);
        exit(1);
    }

    // Parse command line information and set up configurations
    while ((parse_code = getopt(argc, argv, "pfhow:")) != -1)
    {
        switch (parse_code)
        {
            case 'p':
	        in_port = strtol(argv[optind], NULL, 10);
		if (in_port <= 1024 || in_port >= 65536)
                {
		    perror(ERROR_PORT_RANGE);
		    exit(1);
                }
	        break;
            case 'f':
	        f_hostname = argv[optind];
		break;
            case 'h':
	        f_port = strtol(argv[optind], NULL, 10);
		if (f_port <= 1024 || f_port >= 65536)
		{
		    perror(ERROR_PORT_RANGE);
		    exit(1);
		}
		break;
	    case 'o':
	        file_option = argv[optind];
	        break;
	    case 'w':
	        window = strtol(optarg, NULL, 10);
		break;
	    default:
               perror(ERROR_REQUESTER_USAGE);
	       exit(1);
	}
    }

printf ("\033[34mRECEIVER SETUP WITH THE FOLLOWING CONFIG:\033[1;34m\n> in_port:\t%d\n> out_port:\t%d\n> file_opt:\t%s\033[0m\n\n", in_port, f_port, file_option);


    // Create socket for receiver
    if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
    {
        perror(ERROR_SOCKET);
        exit(1);
    }

    // Setup Receiver's server addr
    gethostname(hostname, sizeof(hostname));
    server = gethostbyname(hostname);
    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(in_port);
    bcopy((char *)server->h_addr,
        (char *)&servaddr.sin_addr.s_addr, server->h_length);

    if (bind(sockfd, (struct sockaddr *) &servaddr,
        sizeof(servaddr)) < 0)
    {
        perror(ERROR_BIND);
        exit(1);
    }

    // Do actual receiver work
    dg_recv(sockfd, &cliaddr, sizeof(cliaddr));

    exit(0);
}
